const { sqlite, database } = require("../JDBC");
const { ipcMain } = require("electron");
const windows = require("../../config/windows");

const fs = require("fs");
const Path = require("path");
const zlib = require("zlib");
const Bitwise = require('../BitwiseBuffer'); //按位运算库
const pngBase = require('./pngBase');

const { println } = require("../Console");
const { Console } = require("console");
// const println = console.log;


/**
 * NPK和IMG的处理工具
 * 1 为其他模块提供查询，并返回base64格式的图片
 * 2 索引、模糊查找、增删改查、转png
 */

/**
 * NPK文件结构：
 * [NPK_Header: [校验字节]+[img数量n]  ] +  [NPK_Index]*n =>[img数据包]*n
 *  
 * img数据包结构
 * [nimgF_Header: [校验字节]+[png数量m] ] + [nimgF_index]*m =>[png数据包]*m
 * 
 */


//文件名解密 秘钥
const fileNameBuffer = Buffer.from([
    "puchikon@neople dungeon and fighter ",
    "DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF",
    "DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF",
    "DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF",
    "DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF",
    "DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF",
    "DNFDNFDNFDNFDNFDNFDNFDNFDNFDNFDNF",
    "DNFDNFDNFDNFDNFDNFDNF\x00",
].join(""));

//NPK文件头
class NPK_Header {
    /*string:*/flag = null;// 文件标识 "NeoplePack_Bill"  [16]
    /*int:*/count = 0;     // 包内文件的数目

    constructor(flag, count) {
        this.flag = flag;
        this.count = count;
    }
};

//NPK索引
class NPK_Index {
    /*int:*/offset = 0;  //uint32_t  文件的包内偏移量
    /*int:*/size = 0;    //unit32_t  文件的大小
    /*string:*/name = "";   //char[256] 文件名

    /*NImgF_Header*/ nimgF_header = null;
    /*NImgF_Index */ nimgF_Indexs = [];
}

//IMG文件头
class NImgF_Header {
    /*string:*/flag = null; // 文件标石"Neople Img File" [16]
    /*int:*/index_size = 0;	// 索引表大小，以字节为单位
    /*int:*/unknown1 = 0;
    /*int:*/unknown2 = 0;
    /*int:*/index_count = 0;// 索引表数目
};

//IMG索引
class NImgF_Index {
    /*int:*/dwType = 0;     //目前已知的类型有 0x0E=14(1555格式) 0x0F=15(4444格式) 0x10=16(8888格式) 0x11=17(不包含任何数据，可能是指内容同上一帧)
    /*int:*/dwCompress = 0; // 目前已知的类型有 0x06=6(zlib压缩) 0x05=5(未压缩)
    /*int:*/width = 0;        // 宽度
    /*int:*/height = 0;       // 高度
    /*int:*/size = 0;         // 压缩时size为压缩后大小，未压缩时size为转换成8888格式时占用的内存大小
    /*int:*/key_x = 0;        // X关键点，当前图片在整图中的X坐标
    /*int:*/key_y = 0;        // Y关键点，当前图片在整图中的Y坐标
    /*int:*/max_width = 0;    // 整图的宽度
    /*int:*/max_height = 0;   // 整图的高度，有此数据是为了对齐精灵

    /*Buffer */image = null;
    /*Buffer */png = null;
};

//openImagePacksFile 返回类型
const RETURN_TYPE = {
    "BASE64": 0, //输出base64代码，用于页面显示
    "PNG": 1, //输出png二进制编码，Buffer
    "ARGB": 2, //输出argb纹理， {argbType:String(ARGB1555,ARGB4444,ARGB8888),argbFile:Buffer}, 用于canvas绘图
    "FILE": 3, //输出到指定文件，配合outDirPath参数
    "DB": 4,//建立数据库缓存，不返回任何事件
    "IMGS": 5,//输出npk下的img列表
}

//int32字节码 转10进制
//注意有符号最大21亿  无符号42亿，且没有负数
const hexInt32ToNum = (buffer) => {
    return parseInt(buffer.readUInt32LE(0).toString(10))
}

//字节转latin1
//并去掉多余的 \x00
const hexToLatin1 = (buffer) => {
    return buffer.toString("latin1").replace(/\x00/g, "");
}

//分段读取
//@param filepath
//@param start:int 开始位 按字节数
//@param end:int   结束位 [start,end]闭区间
const readStrem = (filepath, start, end) => new Promise((res, rej) => {
    let data = "";

    const readStream = fs.createReadStream(filepath, {
        start,
        end,
    })

    /**
     * encoding支持类型：
     * ascii     --7 位 ASCII
     * utf8
     * base64
     * binary (=latin1 )   一字节编码 8位
     * hex                  16进制 4位
     */
    readStream.setEncoding('hex'); //读取编码为16进制

    readStream.on('data', chunk => {
        data += chunk;
    })
    readStream.on('end', () => {
        res(Buffer.from(data, "hex"));
    })
    readStream.on('error', err => {
        rej(err);
    })
})

const fileIsExist = (path) => {
    if (!fs.existsSync(path)) {
        return false;
    }

    return fs.statSync(path).isFile();
}

//openImagePacksFile 错误信息返回
const returnError = (msg, token, callback) => {
    let err = new Error(msg)
    console.log("openImagePacksFile", err);

    if (callback) {
        return callback(msg, err)
    }

    if (token && windows.main) {
        return windows.main.webContents.send("npk-reply", token, "openImagePacksFile", err)
    }

    //无法判断调用方，在render主页面输出错误信息
    return Console.println(`错误:openImagePacksFile,${msg}`);
}

//openImagePacksFile 返回结果
const returnRes = (res, token, callback) => {
    if (callback) {
        callback(null, res);
        return;
    }

    if (token) {
        return windows.main.webContents.send("npk-reply", token, false, res)
    }

    //无法判断调用方，在render主页面输出错误信息
    return Console.println(`错误:openImagePacksFile,${res}`);
}

//openImagePacksFile 是否完成了全部任务 @TODO
const returnVaild = (res, cursor) => {
    if (!res || !res.result) {
        return false;
    }

    let flag = true;
    let resResultKeys = Object.keys(res.result);

    //是否所有img都已存在
    flag = resResultKeys.length == cursor.npkHeader.count;

    //每个img数组长度 是否与 对应nimgF_Indexs.length一致
    if (flag) {
        flag = resResultKeys.every((resImgName) => {
            return cursor.npkIndexs.some(cursor_npkIndex => {
                return resImgName == cursor_npkIndex.name
                    && res.result[resImgName].length == cursor_npkIndex.nimgF_Indexs.length;
            })
        })
    }

    if (flag) {
        console.log("任务结束！", res)
    }
    return flag;
}

/**
 * 解析一个.Npk文件
 * @param token:String 任意字符串，可以用uuid生成，用于多任务时区分
 * @param path:String  NPK文件的路径
 * @param retType:Int  返回类型RETURN_TYPE
 * @param outDirPath:String  tetType=3时需要，输出文件夹。 输出时img路径分隔符替换为"_" 不创建新的路径
 * @param callback:function(token,err,res) 回调函数
 * 
 * 当Service调用时,用callback接收处理结果,*****不要填token******
 * 当Render调用时,通过"npk-reply"事件接收处理结果
 * 
 * 返回一个对象
 * {
 *  path:String  npk文件路径
 *  imgCount:Int 包含img数量
 *  result:{
 *      "img_name":[],   //img_name 即img文件的名字，
 *      "img_name":[],   //数组内容为retType指定的类型，按index排列
 *       ...
 *  }
 * }
 *  */
const openImagePacksFile = (token, path, retType, outDirPath, callback) => {
    //游标，保存跨内部函数的变量
    let cursor = {
        npkHeader: null,
        npkIndexs: [],
    }     

    //构建返回结果
    let res = {
        path,
        imgCount: 0,
        result: {},
    }

    if (!fileIsExist(path)) {
        // println(`读取NPK失败,未找到文件:${path}`);
        return returnError(`读取NPK失败,未找到文件:${path}`, token, callback)
    }

    //读取npk文件
    //解析出 NpkHeader和NpkIndex
    readStrem(path, 0, 15).then(buffer => {

        //校验文件首
        let checkBuffer = Buffer.from("NeoplePack_Bill\x00"); //标准文件头
        if (Buffer.compare(checkBuffer, buffer) != 0) {
            return returnError(`读取NPK失败,文件首校验失败:${path}`);
        }

        cursor.npkHeader = new NPK_Header(hexToLatin1(buffer), 0);


        //读取imgCount
        readStrem(path, 16, 19).then(buffer => {
            let imgCount = hexInt32ToNum(buffer);

            if (imgCount <= 0) {
                return returnError(`读取NPK失败,没有找到img，或文件被加密了:${path}`);
            }

            cursor.npkHeader.count = imgCount;
            res.imgCount = imgCount;
            // console.log("NpkHeader", cursor.npkHeader)

            //根据imgCount 循环读取NPK_Index
            for (let i = 0; i < imgCount; i++) {
                // for (let i = 0; i < 1; i++) {
                //一次读取264字节  1-264
                let start = 20 + 264 * i;
                let end = start + 264 - 1;
                readStrem(path, start, end).then(buffer => {
                    // console.log("load the first npkIndex:",buffer.length,buffer);

                    let npkIndex = new NPK_Index();

                    let offsetBuffer = buffer.slice(1 - 1, 4); //slice 从1开始数，(start,end]=>[start-1,end]  左开右闭
                    // console.log("load the first offsetBuffer:",offsetBuffer.length,offsetBuffer);
                    npkIndex.offset = hexInt32ToNum(offsetBuffer);

                    let sizeBuffer = buffer.slice(5 - 1, 8);//从5开始截 5-1
                    npkIndex.size = hexInt32ToNum(sizeBuffer);

                    let nameBuffer = buffer.slice(9 - 1, 264);
                    let xorBuffer = Bitwise.xor(nameBuffer, fileNameBuffer); //异或得到文件名
                    npkIndex.name = hexToLatin1(xorBuffer);

                    cursor.npkIndexs.push(npkIndex);
                    // console.log("读取了一个NpkIndex", npkIndex);

                    //继续读取数据堆中的img
                    loadImagFile(npkIndex);

                }).catch(e => {
                    returnError(`读取NPK_Index失败,${e.toString()},${path}`, token, callback)
                });
            }
        }).catch(e => {
            returnError(`读取NPK_Header.count失败,${e.toString()},${path}`, token, callback)
        });
    }).catch(e => {
        returnError(`读取NPK失败,${e.toString()},${path}`, token, callback)
    })

    //读取img文件
    //解析出 imgHeader和imgIndex
    /*@param  npkIndex NPK_Index*/
    const loadImagFile = (npkIndex) => {
        let start = npkIndex.offset;    //img数据包的开始位置
        let end = npkIndex.offset + npkIndex.size - 1; //img数据包结束的位置 

        //读取NImgF_Header
        //一次性将img数据包全部读出 (包括 imgHeader + imgIndex*n + argb*n)
        readStrem(path, start, end).then(buffer => {
            //校验1-16字节（固定16字节）
            //@TODO 有两种封包格式 Neople Img File[16] 和 Neople Image File[24]
            //这里只实现了一种
            let vaildBuffer = Buffer.from("Neople Img File\x00");
            let flagBuffer = buffer.slice(1 - 1, 16);

            if (Buffer.compare(vaildBuffer, flagBuffer) != 0) {
                return returnError(`读取img失败,img校验码错误，或不支持的img格式，或加密的img格式:${path}`);
            }

            let nimgF_Header = new NImgF_Header();
            nimgF_Header.flag = hexToLatin1(flagBuffer);

            let sizeBuffer = buffer.slice(17 - 1, 20); //4字节 17-20
            nimgF_Header.index_size = hexInt32ToNum(sizeBuffer);

            let u1Buffer = buffer.slice(21 - 1, 24); //4字节 21-24
            nimgF_Header.unknown1 = hexInt32ToNum(u1Buffer);

            let u2Buffer = buffer.slice(25 - 1, 28); //4字节 25-28
            nimgF_Header.unknown2 = hexInt32ToNum(u2Buffer);

            let countBuffer = buffer.slice(29 - 1, 32); //4字节 29-32
            nimgF_Header.index_count = hexInt32ToNum(countBuffer);

            console.log("读取到一个 NImgF_Header", nimgF_Header);

            npkIndex.nimgF_header = nimgF_Header;
            npkIndex.nimgF_Indexs = [];

            //循环读取NImgF_Index
            //33开始, 固定36字节
            for (let i = 0; i < nimgF_Header.index_count; i++) {
                let nimgF_index = new NImgF_Index();
                npkIndex.nimgF_Indexs.push(nimgF_index);

                let imgStart = 33 + i * 36;
                let imgEnd = imgStart + 36;
                let imgBuffer = buffer.slice(imgStart - 1, imgEnd);

                let imgDwTypeBuffer = imgBuffer.slice(0, 3 + 1);
                nimgF_index.dwType = hexInt32ToNum(imgDwTypeBuffer);

                let imgDwComprBuffer = imgBuffer.slice(4, 7 + 1);
                nimgF_index.dwCompress = hexInt32ToNum(imgDwComprBuffer);

                if (nimgF_index.dwType == 17) {
                    // 占位文件只包含dwType,dwCompress两个数据
                    continue;
                }

                let imgWidthBuffer = imgBuffer.slice(8, 11 + 1);
                let imgHeightBuffer = imgBuffer.slice(12, 15 + 1);
                let imgSizeBuffer = imgBuffer.slice(16, 19 + 1);
                let imgKeyXBuffer = imgBuffer.slice(20, 24);
                let imgKeyYBuffer = imgBuffer.slice(24, 28);
                let imgMaxWBuffer = imgBuffer.slice(28, 32);
                let imgMaxHBuffer = imgBuffer.slice(32, 36);

                nimgF_index.width = hexInt32ToNum(imgWidthBuffer);
                nimgF_index.height = hexInt32ToNum(imgHeightBuffer);
                nimgF_index.size = hexInt32ToNum(imgSizeBuffer);
                nimgF_index.key_x = hexInt32ToNum(imgKeyXBuffer);
                nimgF_index.key_y = hexInt32ToNum(imgKeyYBuffer);
                nimgF_index.max_width = hexInt32ToNum(imgMaxWBuffer);
                nimgF_index.max_height = hexInt32ToNum(imgMaxHBuffer);

                //修正size
                /*
                    dwCompress == 5 未压缩
                        => size指8888格式所占内存大小
                            如果dwType == 14 || dwType == 15时  (1555或4444格式，非8888格式)
                           size应为修正为size*0.5

                    dwCompress == 6 用zlib解压
                        => size是压缩后的大小，正常读取即可
                */
                if (nimgF_index.dwCompress == 5 && nimgF_index.dwType != 16) {
                    nimgF_index.size = nimgF_index.size * 0.5
                }

                // console.log("load a NImgF_Index", nimgF_index);
            }//end for

            console.log("load  npkIndex.nimgF_Indexs success")
            // // console.log("npkIndex.nimgF_header.index_count:", nimgF_Header.index_count);
            // // console.log(npkIndex)

            //如果是数据库缓存任务 存一下npk_index
            if (retType == RETURN_TYPE.DB) {
                sql.npkIndex.insert({
                    $path: path,
                    $img: npkIndex.name,
                    $length: nimgF_Header.index_count,
                });
            }

            //下一步读取纹理
            loadARBGFile(buffer, npkIndex, nimgF_Header.index_size, start);


        }).catch(e => {
            returnError(`读取img失败,${e.toString()},${path}`, token, callback)
        });
    }

    //读取ARBG纹理
    //@param imgBuffer:Buffer
    //@param nimgF_Indexs:NImgF_Index
    //@param startOffset:int 偏移地址
    const loadARBGFile = (imgBuffer, npkIndex, index_size, imgOffset) => {
        let nimgF_Indexs = npkIndex.nimgF_Indexs;

        //计算开始位置
        let startOffset = 32 + index_size; //NImgF_Header大小+NImgF_Header.index_size大小  (index_size = 36* index_count)

        //记录上一个图片结束的地址(下一位为下一张图片开始的地址)
        let lastOffset = startOffset;

        nimgF_Indexs.forEach((nimgF_Index, index) => {
            let pngStart = lastOffset;
            let pngEnd = lastOffset = pngStart + nimgF_Index.size;
            // console.log("pngStart",pngStart,"pngEnd",pngEnd)


            //如果是数据库缓存任务 不需要继续取数据了  //校验数据大小
            console.log("slice length:", nimgF_Index.image.length, "nimgF_Index.size:", nimgF_Index.size)
            if (nimgF_Index.image.length != nimgF_Index.size) {
                console.log("error png length")
                return;
            }
            //存一下npk_img表
            if (retType == RETURN_TYPE.DB) {
                if (!res.result[npkIndex.name]) {
                    res.result[npkIndex.name] = [];
                }

                let param = {
                    $path: path,
                    $img: npkIndex.name,
                    $img_index: i,
                    $img_type: nimgF_Index.dwType,
                    $img_comp: nimgF_Index.dwCompress,
                    $img_size: nimgF_Index.size,
                    $img_w: nimgF_Index.width,
                    $img_h: nimgF_Index.height,
                    $img_x: nimgF_Index.key_x,
                    $img_y: nimgF_Index.key_y,
                    $offset: imgOffset + pngStart,
                }

                res.result[npkIndex.name].push(param)
                sqlite.npkImg.insert(param);
                exportType(npkIndex, nimgF_Index, index); //掉用一下exportType 判断是否结束掉任务
                return;
            }


            nimgF_Index.image = imgBuffer.slice(pngStart, pngEnd); //读取出数据


            //解压缩&&写入png文件
            if (nimgF_Index.dwCompress == 6) {
                //压缩数据:
                console.log("find a zlib argb file:")

                zlib.inflate(nimgF_Index.image, (err, inflat_buf) => {
                    if (err) {
                        console.log("error:", err);
                        return;
                    }
                    console.log("zlib:", "解压前大小:", nimgF_Index.image.length, "解压后大小:", inflat_buf.length);
                    if (inflat_buf.length <= 0) {
                        //加密npk
                        console.log("遇到了加密的npk", path, npkIndex.name, index)
                        return;
                    }

                    nimgF_Index.image = inflat_buf;
                    exportType(npkIndex, nimgF_Index, index);

                });

            } else if (nimgF_Index.dwCompress == 5) {
                //未压缩数据:
                console.log("find a unzlib argb file:")
                exportType(npkIndex, nimgF_Index, index);
            }
        });
        // console.log("解析完毕:",nimgF_Indexs);
    }

    //输出选项判断
    const exportType = (npkIndex, nimgF_Index, index) => {

        //如果是数据库任务 判断是否结束
        if (retType == RETURN_TYPE.DB) {


            return;
        }

        //不用继续往下转png了
        if (retType == RETURN_TYPE.ARGB) {

            if (!res.result[npkIndex.name]) {
                res.result[npkIndex.name] = [];
            }

            res.result[npkIndex.name][index] = nimgF_Index.image;

            //判断所有任务是否结束 @TODO
            if (returnVaild(res, cursor)) {
                return returnRes(res, token, callback);
            }

            return;
        }

        //继续转成PNG
        pngBase.getPngFile(nimgF_Index, nimgF_Index.image).then(image => {
            if (retType == RETURN_TYPE.BASE64) {
                image.getBase64Async("image/png").then(base64 => {
                    if (!res.result[npkIndex.name]) {
                        res.result[npkIndex.name] = [];
                    }

                    res.result[npkIndex.name][index] = base64;

                    //判断所有任务是否结束 @TODO
                    if (returnVaild(res, cursor)) {
                        returnRes(res, token, callback);
                    }
                })
                return;
            }

            if (retType == RETURN_TYPE.PNG) {
                image.getBufferAsync("image/png").then(buffer => {
                    if (!res.result[npkIndex.name]) {
                        res.result[npkIndex.name] = [];
                    }

                    res.result[npkIndex.name][index] = buffer;

                    //判断所有任务是否结束 @TODO
                    if (returnVaild(res, cursor)) {
                        returnRes(res, token, callback);
                    }
                })
                return;

            }

            if (retType == RETURN_TYPE.FILE) {
                //创建文件夹
                let imgDirPath = npkIndex.name.replaceAll("/", "_").replaceAll("\\", "_").replace(".img", "_img")
                let outImgPath = Path.join(outDirPath, imgDirPath);
                if (!fs.existsSync(outDirPath)) {
                    fs.mkdirSync(outDirPath);
                }

                image.writeAsync(Path.join(outImgPath, `${index}.png`)).then(() => {
                    if (!res.result[npkIndex.name]) {
                        res.result[npkIndex.name] = [];
                    }

                    res.result[npkIndex.name][index] = true;

                    //判断所有任务是否结束 @TODO
                    if (returnVaild(res, cursor)) {
                        returnRes(res, token, callback);
                    }
                }).catch(err => {
                    console.log(err);

                    if (!res.result[npkIndex.name]) {
                        res.result[npkIndex.name] = [];
                    }

                    res.result[npkIndex.name][index] = false;

                    //判断所有任务是否结束 @TODO
                    if (returnVaild(res, cursor)) {
                        returnRes(res, token, callback);
                    }
                })
                return;
            }
        })
    }
}

module.exports = {
    openImagePacksFile,
    RETURN_TYPE,
};

